﻿/******************************************************************************
 * DETI INTERACT
 * 
 * Departamento de Electronica, Telecomunicacoes e Informatica
 * Universidade de Aveiro
 * 
 * Author: Filipe Duarte
 ******************************************************************************/

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Threading;
using DetiInteract.Guide.Controls;
using DetiInteract.Guide.Controls.ContentControls.Viewer3DControl;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Media.Animation;
using System.Windows.Input;

namespace DetiInteract.Guide
{
	sealed class MainViewModel : DependencyObject
	{
		#region Dependency Properties
		/// <summary>
		/// Dependency Property that will hold a Time string for the Header.
		/// </summary>
		public string Time
		{
			get { return (string)GetValue(TimeProperty); }
			set { SetValue(TimeProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Time.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty TimeProperty =
			DependencyProperty.Register("Time", typeof(string), typeof(MainViewModel), new UIPropertyMetadata(""));

		/// <summary>
		/// Dependency Property that will hold a Date string for the Header.
		/// </summary>
		public string Date
		{
			get { return (string)GetValue(DateProperty); }
			set { SetValue(DateProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Date.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty DateProperty =
			DependencyProperty.Register("Date", typeof(string), typeof(MainViewModel), new UIPropertyMetadata(""));

		#endregion

		#region Fields

		/// <summary>
		/// Timer that controls Time and Date updates.
		/// </summary>
		private Timer _dateTimer;

		/// <summary>
		/// Timer used to check for user inactivity.
		/// </summary>
		private Timer _inactivityTimer;

		private double _time = 0;

		/// <summary>
		/// Indicates when a page can be switched.
		/// </summary>
		private bool _canSwitchPage = false;

		/// <summary>
		/// Event triggered when no user activity has been detected for a 
		/// period of time;
		/// </summary>
		private event EventHandler _systemIsInactive;

		/// <summary>
		/// A list of all the controls included in the main window.
		/// </summary>
		public ObservableCollection<DetiControlView> DetiControls { get; set; }

		/// <summary>
		/// Keeps track of the currently displayed control.
		/// </summary>
		private int _activeControl = 0;

		/// <summary>
		/// Controller that allows remote interaction with the system.
		/// </summary>
		private Control.Controller _controller;

		private bool _animating = false;

		/// <summary>
		/// Structure to store last input time.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
		public struct LASTINPUTINFO
		{
			public uint cbSize;
			public uint dwTime;
		}

		/// <summary>
		/// Get the time for the last input.
		/// </summary>
		/// <param name="plii"></param>
		/// <returns></returns>
		[DllImport("user32.dll")]
		static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

		#endregion

		/// <summary>
		/// Constructor
		/// </summary>
		public MainViewModel()
		{
			DetiControls = new ObservableCollection<DetiControlView>();

			// Set overall application framerate
			//Timeline.DesiredFrameRateProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata { DefaultValue = 20 });

			// Hide mouse cursor
			Mouse.OverrideCursor = Cursors.None;

			// Configure and start the date timer
			SetUpDateTimer();

			// Configure and start the user inactivity timer
			SetUpInactivityTimer();

			// Add controls to the windows
			SetUpWindowControls();

			// Set up bluetooth communication
			SetUpController();
		}

		#region Controller
		/// <summary>
		/// Initializes the controller and configures its callback handlers.
		/// </summary>
		private void SetUpController()
		{
			try
			{
				_controller = new Control.Controller();
				_controller.Init();

				// Set up the Gesture handlers
				_controller.Tap += new System.ComponentModel.ProgressChangedEventHandler(_controller_Tap);
				_controller.LongPress += new System.ComponentModel.ProgressChangedEventHandler(_controller_LongPress);
				_controller.Scroll += new System.ComponentModel.ProgressChangedEventHandler(_controller_Scroll);
				_controller.Fling += new System.ComponentModel.ProgressChangedEventHandler(_controller_Fling);
				_controller.Rotation += new System.ComponentModel.ProgressChangedEventHandler(_controller_Rotation);
				_controller.Zoom += new System.ComponentModel.ProgressChangedEventHandler(_controller_Zoom);
				_controller.Connected += new System.ComponentModel.ProgressChangedEventHandler(_controller_Connected);
				_controller.Disconnected += new System.ComponentModel.ProgressChangedEventHandler(_controller_Disconnected);

				DetiInteract.Logger.Log.Instance.Write(this, "INFO", "Controller Configured");
			}
			// If computer has no bluetooth or some error happened
			catch (Exception e)
			{
				DetiInteract.Logger.Log.Instance.Write(this, "Error", "Controller not configured");
			}
		}

		/// <summary>
		/// Handles a Connected event.
		/// Stops the inactivity timer and any ongoing animation.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Connected(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			if (_inactivityTimer != null)
			{
				_inactivityTimer.Dispose();
			}

			foreach (DetiControlView c in DetiControls)
			{
				c.GetControl().StopAnimation();
			}
			//DetiControls[_activeControl].GetControl().StopAnimation();
			_animating = false;

			_activeControl = 0;
			DetiControls[_activeControl].Expand();

			DetiInteract.Logger.Log.Instance.Write(this, "USER", String.Format("User: {0} Connected!", (string)e.UserState));
		}

		/// <summary>
		/// Handles a Disconnected event.
		/// Starts the inactivity timer.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Disconnected(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			DetiInteract.Logger.Log.Instance.Write(this, "USER", "User Disconnected.");

			_inactivityTimer = new Timer(new TimerCallback(_inactivityTimer_Tick), null, 0, 10000);
		}

		/// <summary>
		/// Handles a LongPress gesture.
		/// Exectues the LongPress method on whichever control is visible.
		/// </summary>	
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_LongPress(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			DetiControls[_activeControl].GetControl().LongPress();
		}

		/// <summary>
		/// Handles a Tap gesture.
		/// Exectues the Tap method on whichever control is visible.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Tap(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			DetiControls[_activeControl].GetControl().Tap();
		}

		/// <summary>
		/// Handles a Rotation gesture.
		/// Expands the control to the left or right, according to device tilt.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Rotation(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			Control.GestureData.RotationData rd = e.UserState as Control.GestureData.RotationData;

			DetiControls[_activeControl].GetControl().Rotation(rd.x, rd.y, rd.z);

			//If CanSwitchPage is false, return;
			if (!DetiControls[_activeControl].GetControl().CanSwitchPage()) return;

			if ((rd.z > -10) && (rd.z < 10))
				_canSwitchPage = true;

			//If _canSwitchPage is false, return;
			if (!_canSwitchPage) return;

			// If device is tilted to the left
			if (rd.z > 40)
			{
				if (_activeControl > 0)
				{
					_activeControl -= 1;
					DetiControls[_activeControl].Expand();
					DetiControls[_activeControl].GetControl().ControlLoaded();
					_canSwitchPage = false;

					DetiInteract.Logger.Log.Instance.Write(this, "USER", "Swiched to Page on the Left.");

					return;
				}
			}
			// If device is tilted to the right
			else if (rd.z < -40)
			{
				if (_activeControl < DetiControls.Count - 1)
				{
					_activeControl += 1;
					DetiControls[_activeControl].Expand();
					DetiControls[_activeControl].GetControl().ControlLoaded();
					_canSwitchPage = false;

					DetiInteract.Logger.Log.Instance.Write(this, "USER", "Swiched to Page on the Right.");

					return;
				}
			}
		}

		/// <summary>
		/// Handles a Scroll gesture.
		/// Exectues the Scroll method on whichever control is visible.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Scroll(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			Control.GestureData.ScrollData sd = e.UserState as Control.GestureData.ScrollData;

			DetiControls[_activeControl].GetControl().Scroll(sd.X, sd.Y);
		}

		/// <summary>
		/// Handles a Fling gesture.
		/// Exectues the Fling method on whichever control is visible.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Fling(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			Control.GestureData.ScrollData sd = e.UserState as Control.GestureData.ScrollData;

			DetiControls[_activeControl].GetControl().Fling(sd.X, sd.Y);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _controller_Zoom(object sender, System.ComponentModel.ProgressChangedEventArgs e)
		{
			float scale = (float)e.UserState;

			DetiControls[_activeControl].GetControl().Zoom(scale);
		}
		#endregion

		#region Timer-related operations
		/// <summary>
		/// Configures the Timer that updates the Time and Date properties.
		/// </summary>
		private void SetUpDateTimer()
		{
			_dateTimer = new Timer(new TimerCallback(_dateTimer_Tick), null, 0, 60000);
		}

		/// <summary>
		/// Tick event handler for the DateTimer.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _dateTimer_Tick(object state)
		{
			// update values
			Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
			{
				Time = DateTime.Now.ToString("HH:mm");
				Date = DateTime.Now.ToString("D");
			}));
		}

		/// <summary>
		/// Configures the Timer that checks for user inactivity.
		/// </summary>
		private void SetUpInactivityTimer()
		{
			_inactivityTimer = new Timer(new TimerCallback(_inactivityTimer_Tick), null, 0, 10000);

			// Set up the inactivity notifier callback
			_systemIsInactive += new EventHandler(MainViewModel__systemIsInactive);
		}

		/// <summary>
		/// Tick event handler for the inactivity Timer.
		/// Triggers the _systemIsInactive event.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void _inactivityTimer_Tick(object state)
		{
			// Notify the system of user inactivity.
			Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
			{
				if (_systemIsInactive != null) _systemIsInactive(this, null);
			}));

		}

		#endregion

		#region MainView Management
		/// <summary>
		/// Visits each control on the observable collection and configures its
		/// dimensions.
		/// </summary>
		private void AdjustControlSize()
		{
			// Calculate width and height
			int width = (int)System.Windows.SystemParameters.PrimaryScreenWidth - ((DetiControls.Count) * 28) - 10 - 10;
			int height = (int)System.Windows.SystemParameters.PrimaryScreenHeight - 100 - 10;

			// Set dimensions for each control
			foreach (DetiControlView c in DetiControls)
			{
				c.GetControl().SetDimensions(width, height);
			}
		}

		/// <summary>
		/// Creates all the controls to be displayed on the MainView
		/// </summary>
		private void SetUpWindowControls()
		{
			AddWindowControl(new DetiControlView(new HelpView(), "ajuda"));
			AddWindowControl(new DetiControlView(new TeacherView(), "docentes"));
			AddWindowControl(new DetiControlView(new TimetableControl(), "horários"));
			AddWindowControl(new DetiControlView(new GoogleMapsControl(), "google earth"));
			//AddWindowControl(new DetiControlView(new Viewer3DView(), "Visualizador 3D"));

			_activeControl = 1;
			DetiControls[_activeControl].Expand();
		}

		/// <summary>
		/// Handles the ControlExpanded event of the DetiControlView.
		/// Collapses all but the Control that was selected.
		/// </summary>
		/// <param name="sender">DetiControlView that was selected</param>
		/// <param name="e">Event Args</param>
		void ControlExpanded(object sender, RoutedEventArgs e)
		{
			// Get the control that was clicked
			DetiControlView control = sender as DetiControlView;

			if (control != null)
			{
				// Collapse every other control
				foreach (DetiControlView otherControl in DetiControls)
				{
					if (otherControl != control)
						otherControl.Collapse();
				}

				// update the index of the currently displayed control.
				_activeControl = DetiControls.IndexOf(control);
			}
		}

		/// <summary>
		/// Inserts the given DetiControlView in the DetiControls collection, 
		/// automatically updating the ListBox in the MainView.
		/// </summary>
		/// <param name="control"></param>
		private void AddWindowControl(DetiControlView control)
		{
			// Collapse control
			control.Collapse();

			// Set ControlExpanded callback handler
			control.ControlExpanded += new RoutedEventHandler(ControlExpanded);
			control.GetControl().AnimationEnd += new EventHandler(DetiControl_AnimationEnd);

			// Add to the collection.
			DetiControls.Add(control);
			AdjustControlSize();
		}
		#endregion

		#region Animation
		/// <summary>
		/// Handles the _systemIsInactive event.
		/// Resets the view to the first control on the colletion and triggers
		/// the beginning of its animation.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void MainViewModel__systemIsInactive(object sender, EventArgs e)
		{
			// display the first control.
			_activeControl = 1;

			// stop the inactivity timer from interfering with the demo mode.
			if (_inactivityTimer != null)
			{
				_inactivityTimer.Dispose();
			}

			DetiInteract.Logger.Log.Instance.Write(this, "Info", "Demo mode.");

			_animating = true;

			// expand the first control and initiate animation.
			DetiControls[_activeControl].Expand();
			DetiControls[_activeControl].GetControl().ControlLoaded();
			DetiControls[_activeControl].GetControl().StartAnimation();
		}

		/// <summary>
		/// Handles the AnimationEnd for each control.
		/// Selects the next control on the collection an initiates its 
		/// animation routine.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void DetiControl_AnimationEnd(object sender, EventArgs e)
		{
			// select next control
			_activeControl += 1;

			if (_activeControl >= DetiControls.Count)
			{
				_activeControl = 1;
			}

			// expands control and initiates animation.
			DetiControls[_activeControl].Expand();
			DetiControls[_activeControl].GetControl().ControlLoaded();
			DetiControls[_activeControl].GetControl().StartAnimation();

		}
		#endregion
	}
}
